package com.stardata.liangshisq.tcslmon.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.stardata.liangshisq.tcslmon.entity.ArchItem;
import com.stardata.liangshisq.tcslmon.entity.BizBs;
import com.stardata.liangshisq.tcslmon.entity.KcKsc;
import com.stardata.liangshisq.tcslmon.entity.WindowItemQueueVo;
import com.stardata.liangshisq.tcslmon.mapper.ArchItemMapper;
import com.stardata.liangshisq.tcslmon.mapper.BizBsMapper;
import com.stardata.liangshisq.tcslmon.mapper.KcKscMapper;
import com.stardata.liangshisq.tcslmon.service.ItemQueueService;
import com.stardata.liangshisq.tcslmon.service.OrderQueueService;
import com.stardata.liangshisq.tcslmon.service.SysParameterService;
import com.stardata.liangshisq.tcslmon.util.CommonConstants;
import com.stardata.liangshisq.tcslmon.util.JSONUtil;
import lombok.AllArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Samson Shu
 * @email shush@stardata.top
 * @date 2020/12/13 15:36
 */
@Service
@AllArgsConstructor
public class ItemQueueServiceImpl implements ItemQueueService {

  private final Logger logger = LoggerFactory.getLogger(this.getClass());


  private final OrderQueueService orderQueueService;
  private final SysParameterService parameterService;
  private final KcKscMapper kcKscMapper;
  private final ArchItemMapper archItemMapper;
  private final BizBsMapper bizBsMapper;

  private final SimpMessagingTemplate messagingTemplate;

  @Override
  public void refresh() throws JsonProcessingException {

    //1. 将所有"待配菜"的成品熟食标记为"已配菜"
    List<Long> cookedClassIds  = parameterService.getListByCode("cookedClassIds", Long.class);
    if (cookedClassIds.size()>0) {
      kcKscMapper.setCookedPrepared(cookedClassIds);
    }
    // 打包盒直接设置为以及传完菜
    List<Long> serveClassIds  = parameterService.getListByCode("serveClassIds", Long.class);
    if (cookedClassIds.size()>0) {
      kcKscMapper.setCookedServe(cookedClassIds);
    }
    KitchenQueueUtil kitchenQueueUtil = KitchenQueueUtil.getInstance();

    //2. 读取配菜窗口队列到 windowList
    List<String> prepareWindowNames = kitchenQueueUtil.getPrepareWindowMames();
    List<List<WindowItemQueueVo>> allWindowList = new ArrayList<>();
    for (String windowName : prepareWindowNames) {
      List<WindowItemQueueVo> windowList = parameterService.getListByCode(windowName, WindowItemQueueVo.class);
      allWindowList.add(windowList);
    }

    //3. 将windowList中已经退单的菜品删除
    for (List<WindowItemQueueVo> windowList : allWindowList) {
      for (int j = windowList.size() - 1; j >= 0; j--) {
        WindowItemQueueVo item = windowList.get(j);
        List<Long> itemKcKscIds = item.getKscIds();
        List<Long> itemBsIds = item.getBsIds();
        List<Boolean> itemIsJumped = item.getIsJumped();
        for (int k = itemKcKscIds.size() - 1; k > -1; k--) {
          Long kcKscId = itemKcKscIds.get(k);
          QueryWrapper<KcKsc> kcKscQueryWrapper = new QueryWrapper<>();
          kcKscQueryWrapper.eq("id", kcKscId);
          KcKsc kcKsc = kcKscMapper.selectOne(kcKscQueryWrapper);
          if ((kcKsc == null) || isCancelledKcKsc(kcKsc)) {
            itemKcKscIds.remove(k);
            itemBsIds.remove(k);
            itemIsJumped.remove(k);
          }
        }
        item.setCount(itemKcKscIds.size());
        if (item.getKscIds().size() == 0) {
          windowList.remove(j);
        }
      }
    }

    //4. 清空已经全配的窗口列表
    boolean allFulled = true;
    for (int i = 0; i < allWindowList.size(); i++) {
      List<WindowItemQueueVo> windowList = allWindowList.get(i);
      List<Long> windowKcKscIds = new ArrayList<>();
      for (WindowItemQueueVo item : windowList) {
        windowKcKscIds.addAll(item.getKscIds());
      }
      if (windowKcKscIds.size()>0) {
        QueryWrapper<KcKsc> kcKscQueryWrapper = new QueryWrapper<>();
        kcKscQueryWrapper.in("id", windowKcKscIds)
            .eq("kitchen_flg", CommonConstants.KitchenFlg.TO_PREPARE);
        List<KcKsc> kcKscList = kcKscMapper.selectList(kcKscQueryWrapper);
        if (kcKscList.size() < 1) {
          windowList.clear();
          allFulled = false;
        } else {
          // 标记是否窗口已满
          if (!kitchenQueueUtil.windowIsFulled(windowList, i)) {
            allFulled = false;
          }
        }
      } else {
        allFulled = false;
      }
    }

    // 如果不是所有当前配菜窗口已满，则重新计算
    if(!allFulled) {
      //5. 刷新订单排队
      orderQueueService.refresh();

      //6. 如果有待配菜的订单，则根据“配菜中”订单的所有待配菜品计算配菜窗口、并更新菜品等待列表
      final List<Long> preparingOrderIds = parameterService.getListByCode("preparingOrderQueue", Long.class);
      List<Long> waitingKcKscIds;
      if (preparingOrderIds.size() > 0) {
        waitingKcKscIds = updateWindowsByOrderIds(allWindowList, preparingOrderIds);
      } else {
        waitingKcKscIds = new ArrayList<>();
      }

      //7. 保存配菜窗口、以及菜品等待列表
      saveAllPrepareWindow(prepareWindowNames, allWindowList);
      parameterService.saveByCode("itemWaitingList", JSONUtil.toJSONString(waitingKcKscIds));
    }
  }

  @Override
  public void orderItemRefresh() {
    List<Long> serveClassIds  = parameterService.getListByCode("serveClassIds", Long.class);

    //1. 检查未完成的订单信息， 检查菜品是否全部上万,除了打包盒
    QueryWrapper<BizBs> bsQueryWrapper = new QueryWrapper<>();
    bsQueryWrapper.eq("state", 0);
    List<BizBs> bizBsList = bizBsMapper.selectList(bsQueryWrapper);
    for(BizBs bizBs:bizBsList){
      // 查询未完成的订单
      QueryWrapper<KcKsc> kcKscWrapper = new QueryWrapper<>();
      kcKscWrapper.eq("bs_id", bizBs.getId())
              .ne("kitchen_flg", CommonConstants.KitchenFlg.PRODUCED)
              .notIn("item_Id", serveClassIds);
      List<KcKsc> kcKscList = kcKscMapper.selectList(kcKscWrapper);
      if(kcKscList.isEmpty()){
        bizBs.setState(CommonConstants.OrderStatus.SETTLED);
        bizBs.setModifyTime(LocalDateTime.now());
        bizBsMapper.updateById(bizBs);
      }
    }
    //2 检查未完成的菜品信息的订单是否完成， 若没有完成需要打开订单的状态
    QueryWrapper<KcKsc> kcKscWrapper = new QueryWrapper<>();
    kcKscWrapper.ne("kitchen_flg", CommonConstants.KitchenFlg.PRODUCED)
            .notIn("item_Id", serveClassIds);
    List<KcKsc> kcKscList = kcKscMapper.selectList(kcKscWrapper);
    ArrayList<Long> bizIds = new ArrayList<>();
    for(KcKsc kc:kcKscList){
      if(!bizIds.contains(kc.getBsId())){
        bizIds.add(kc.getBsId());
      }
    }
    // 若新加菜品 则修改为0
    kcKscMapper.updateBizBsStatusByIds(bizIds);
    try {
      messagingTemplate.convertAndSend("/prepare_refresh", "");
      messagingTemplate.convertAndSend("/passon_refresh", "");
    } catch (Exception e) {
      logger.error("ERROR ==> 刷新订单排队时出现JSON格式错误", e);
    }




  }

  @Override
  public void messageSend() {
    try {
      messagingTemplate.convertAndSend("/prepare_refresh", "");
      messagingTemplate.convertAndSend("/passon_refresh", "");
    } catch (Exception e) {
      logger.error("ERROR ==> 刷新订单排队时出现JSON格式错误", e);
    }
  }

  @NotNull
  private List<Long> updateWindowsByOrderIds(List<List<WindowItemQueueVo>> allWindowList, List<Long> preparingOrderIds) {
    KitchenQueueUtil kitchenQueueUtil = KitchenQueueUtil.getInstance();
    QueryWrapper<KcKsc> kcKscQueryWrapper = new QueryWrapper<>();
    kcKscQueryWrapper.in("bs_id", preparingOrderIds)
        .eq("kitchen_flg", CommonConstants.KitchenFlg.TO_PREPARE)
        .orderByAsc("create_time")
        //只考虑8小时内的后厨流水
        .apply("EXTRACT(epoch from (now()-create_time)) < 8*3600");
    List<KcKsc> preparingKcKsc = kcKscMapper.selectList(kcKscQueryWrapper);
    //1. 删除所有实际数量为0的项目
    for (int i = preparingKcKsc.size() - 1; i >= 0; i--) {
      KcKsc kcKsc = preparingKcKsc.get(i);
      if (isCancelledKcKsc(kcKsc)) {
        preparingKcKsc.remove(i);
      }
    }
    //2. 如果菜品的实际业务流水已经结算，则删除
    for (int i = preparingKcKsc.size() - 1; i >= 0; i--) {
      KcKsc kcKsc = preparingKcKsc.get(i);
      BizBs bizBs = bizBsMapper.selectById(kcKsc.getBsId());
      if (bizBs == null || bizBs.getState().equals(CommonConstants.OrderStatus.SETTLED)) {
        preparingKcKsc.remove(i);
      }
    }
    //3. 需要将所有的菜品关联上其菜品档案对象，并将临时菜品进行标记。
    // 并且，如果菜品既不是KDS菜品，也不是临时菜品，则删除该记录
    KitchenQueryUtil.clearKcKscForNotKitchen(preparingKcKsc, archItemMapper);

    //4. 按preparingOrderList顺序、菜品下单顺序排列
    preparingKcKsc.sort((o1, o2) -> {
      //先按照按preparingOrderList内订单顺序排序
      Long bsId1 = o1.getBsId();
      Long bsId2 = o2.getBsId();
      int idx1 = preparingOrderIds.indexOf(bsId1);
      int idx2 = preparingOrderIds.indexOf(bsId2);
      if (idx1 < idx2) {
        return -1;
      } else if (idx1 > idx2) {
        return 1;
      }

      //在订单编号相同时，按照菜品下单时间排序
      LocalDateTime dt1 = o1.getCreateTime();
      LocalDateTime dt2 = o2.getCreateTime();
      if (dt1.isBefore(dt2)) {
        return -1;
      } else if (dt1.isAfter(dt2)) {
        return 1;
      }
      return 0;
    });

    //5. 对所有配菜窗口进行处理，尽可能的将"待配菜"菜品合并到对应窗口
    List<Long> waitingKcKscIds = new ArrayList<>();
    List<Long> usedClassIds = new ArrayList<>();
    for (int i = 0; i < allWindowList.size(); i++) {
      List<Long> windowClassIds  = kitchenQueueUtil.getPrepareWindowClassIds().get(i);
      //如果该窗口的类别编码列表为空，则表示该窗口接收所有类别。
      // （为此，需要将其它指定类别的窗口放在前面，这个接收所有类别的窗口必须放最后）
      boolean needAllClass = (windowClassIds.size()==0);

      //5.1 取出该窗口对应的所有待配菜菜品列表
      List<KcKsc> prepareingKsc4Window = new ArrayList<>();
      for (KcKsc kcKsc : preparingKcKsc) {
        ArchItem archItem = kcKsc.getArchItem();

        if ((archItem == null) && needAllClass){
          prepareingKsc4Window.add(kcKsc);
        }
        else if ((archItem != null) && !usedClassIds.contains(archItem.getSmallClassId())){
          if (needAllClass) {
            prepareingKsc4Window.add(kcKsc);
          } else if (windowClassIds.contains(archItem.getSmallClassId())) {
            prepareingKsc4Window.add(kcKsc);
          }
        }
      }

      //5.2 将该窗口的待配菜菜品合并到配菜窗口，直到该窗口已满，或不再有新菜品
      List<WindowItemQueueVo> windowList = allWindowList.get(i);
      queueWindowItems(windowList, preparingOrderIds, prepareingKsc4Window, i);

      //一个窗口的菜品类别被用过后，后面就不能再用了，否则后面配菜窗口会出现前面配菜窗口的重复记录
      usedClassIds.addAll(windowClassIds);

      //5.3. 将该窗口未合并成功的待配菜品保存入配菜等待队列
      for (KcKsc kcKsc : prepareingKsc4Window) {
        waitingKcKscIds.add(kcKsc.getId());
      }

    }
    // 返回等待菜品id列表
    return waitingKcKscIds;
  }

  private void saveAllPrepareWindow(@NotNull List<String> prepareWindowNames, @NotNull List<List<WindowItemQueueVo>> allWindowList) {
    for (int i = 0; i < prepareWindowNames.size(); i++) {
      String windowName = prepareWindowNames.get(i);
      parameterService.saveByCode(windowName, JSONUtil.toJSONString(allWindowList.get(i)));
    }
  }

  private void queueWindowItems(@NotNull List<WindowItemQueueVo> windowList, @NotNull List<Long> preparingOrderList,
                                @NotNull List<KcKsc> preparingKcKsc, int windowIndex) {

    KitchenQueueUtil kitchenQueueUtil = KitchenQueueUtil.getInstance();
    //0. 首先，删除待配菜列表中，所有已经在配菜窗口存在的菜品项
    for (WindowItemQueueVo windowItemQueueVo : windowList) {
      List<Long> windowKscIds = windowItemQueueVo.getKscIds();
      for (int i = preparingKcKsc.size() - 1; i >= 0; i--) {
        KcKsc kcKsc = preparingKcKsc.get(i);
        if (windowKscIds.contains(kcKsc.getId())) {
          preparingKcKsc.remove(i);
        }
      }
    }

    if (kitchenQueueUtil.windowIsFulled(windowList, windowIndex)) return;

    //1. 将"配菜中"订单的前 prioritySize 个订单所含的待配菜品合并到 windowList
    //1.1 读取"配菜中"订单的前 prioritySize 个订单所含的待配菜品列表
    List<KcKsc> toMergeList = new ArrayList<>();
    for (int i = 0; i < kitchenQueueUtil.getPrioritySize(); i++) {
      if (i> preparingOrderList.size()-1) break;
      Long bsId = preparingOrderList.get(i);
      for (KcKsc kcKsc : preparingKcKsc) {
        if (kcKsc.getBsId().equals(bsId)) {
          toMergeList.add(kcKsc);
        }
      }
    }

    //1.2 将列表内容合并到 windowList
    boolean finished = kitchenQueueUtil.mergeKcKscListToWindowList(windowList, toMergeList, windowIndex);

    //2 读取"配菜中"订单的后面所有配菜中菜品列表（按订单下单时间、菜品下单时间排序）
    List<KcKsc> toMergeSurplusList = new ArrayList<>();
    for (int i = kitchenQueueUtil.getPrioritySize(); i < preparingOrderList.size(); i++) {
      Long bsId = preparingOrderList.get(i);
      for (KcKsc kcKsc : preparingKcKsc) {
        if (kcKsc.getBsId().equals(bsId)) {
          toMergeSurplusList.add(kcKsc);
        }
      }
    }
    //3. 如果窗口队列未满，则将"配菜中"订单在 prioritySize 之后订单，所含的待配菜品尽可能合并到 windowList
    if (!finished) {
      kitchenQueueUtil.mergeKcKscListToWindowList(windowList, toMergeSurplusList, windowIndex);
    }

    //4. 将剩下的"配菜中"订单的未配菜品清单，合并在一起，并将已经合并的项目从 preparingKcKsc 删除
    List<Long> prepareingKscIds = new ArrayList<>();
    toMergeList.addAll(toMergeSurplusList);
    for (KcKsc kcKsc : toMergeList) {
      prepareingKscIds.add(kcKsc.getId());
    }
    for (int i = preparingKcKsc.size() - 1; i >= 0; i--) {
      if (!prepareingKscIds.contains(preparingKcKsc.get(i).getId())) {
        preparingKcKsc.remove(i);
      }
    }
  }

  private boolean isCancelledKcKsc(@NotNull KcKsc kcKsc){
    return kcKsc.getLastQty().doubleValue()<0.001;
  }




}
